////////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c) 2010 Gabriel Montagné Láscaris-Comneno and Alberto
//  Brealey-Guzmán.
//
//  Permission is hereby granted, free of charge, to any person obtaining a
//  copy of this software and associated documentation files (the "Software"),
//  to deal in the Software without restriction, including without limitation
//  the rights to use, copy, modify, merge, publish, distribute, sublicense,
//  and/or sell copies of the Software, and to permit persons to whom the
//  Software is furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in
//  all copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
//  THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
//  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
//  DEALINGS IN THE SOFTWARE.
//
////////////////////////////////////////////////////////////////////////////////

package com.rojored.controls
{

import com.rojored.controls.dragSurfaceClasses.DragSurfaceEvent;
import com.rojored.controls.dragSurfaceClasses.DragSurfaceRangeDirection;
import com.rojored.controls.dragSurfaceClasses.DragSurfaceRangeSkin;
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.utils.getQualifiedClassName;
import mx.logging.ILogger;
import mx.logging.Log;
import mx.utils.StringUtil;
import spark.components.Label;
import spark.components.supportClasses.Range;

//--------------------------------------
//   Events
//--------------------------------------

/**
 *  Packs a DragSurface component inside a Range component to be able to use
 *  it as drop-in replacement for Slider, NumericStepper, etc.
 */
public class DragSurfaceRange extends Range
{

    //--------------------------------------------------------------------------
    //
    //  Constructor
    //
    //--------------------------------------------------------------------------

    /**
     *  Constructor
     */
    public function DragSurfaceRange()
    {
        super();
        setStyle("skinClass", DragSurfaceRangeSkin);
    }

    //--------------------------------------------------------------------------
    //
    //  Variables
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private var logger:ILogger =
        Log.getLogger(getQualifiedClassName(this).replace(/\W+/g, "."));

    /**
     *  @private
     */
    private var dragValueDirty:Boolean;

    /**
     *  @private
     */
    private var userInteraction:Boolean;

    //--------------------------------------------------------------------------
    //
    //  SkinParts
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    //  dragSurface
    //--------------------------------------

    [SkinPart(required="true")]

    /**
     *  A skin part that defined the DragSurface component that will collect
     *  the data from the user interactions.
     */
    public var dragSurface:DragSurface;

    //--------------------------------------
    //  debugLabel
    //--------------------------------------

    [SkinPart(required="false")]

    /**
     *  If defined, the debug text will display control vars, etc. for
     *  debugging purposes.
     */
    public var debugLabel:Label;

    //--------------------------------------------------------------------------
    //
    //  Overriden properties: Range
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    // minimum
    //--------------------------------------

    /**
     *  @private
     */
    private var minimumChanged:Boolean;

    [Bindable("minimumChanged")]

    /**
     *  The minimum valid <code>value</code>.
     */
    override public function get minimum():Number
    {
        return super.minimum;
    }

    /**
     *  @private
     */
    override public function set minimum(value:Number):void
    {
        if (super.minimum == value)
            return;

        super.minimum = value;
        minimumChanged = true;
        invalidateProperties();
        dispatchEvent(new Event("minimumChanged"));
    }

    //--------------------------------------
    // maximum
    //--------------------------------------

    /**
     *  @private
     */
    private var maximumChanged:Boolean;

    [Bindable("maximumChanged")]

    /**
     *  The maximum valid <code>value</code>.
     */
    override public function get maximum():Number
    {
        return super.maximum;
    }

    /**
     *  @private
     */
    override public function set maximum(value:Number):void
    {
        if (super.maximum == value)
            return;

        super.maximum = value;
        maximumChanged = true;
        invalidateProperties();
        dispatchEvent(new Event("maximumChanged"));
    }

    //--------------------------------------------------------------------------
    //
    //  Properties
    //
    //--------------------------------------------------------------------------

    //--------------------------------------
    //  direction
    //--------------------------------------

    /**
     *  @private
     *  Storage for the direction property
     */
    private var _direction:String = DragSurfaceRangeDirection.HORIZONTAL;

    /**
     *  @private
     */
    private var directionChanged:Boolean;

    [Bindable("directionChanged")]

    /**
     *  The direction in which the DragSurface understands it's value.
     */
    public function get direction():String
    {
        return _direction;
    }

    /**
     *  @private
     */
    public function set direction(value:String):void
    {
        if (_direction == value)
            return;

        _direction = value;
        invalidateProperties();
        invalidateDisplayList();
        dispatchEvent(new Event("directionChanged"));
    }

    //--------------------------------------------------------------------------
    //
    //  Overriden methods: Skinnable component
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function partAdded(partName:String,
                                          instance:Object):void
    {
        super.partAdded(partName, instance);
        if (instance == dragSurface)
        {
            dragSurface.incrementalHorizontalIndex = false;
            dragSurface.incrementalVerticalIndex = false;
            dragSurface.addEventListener(
                DragSurfaceEvent.HORIZONTAL_INDEX_CHANGED,
                dragSurface_horizontalIndexChangedHandler
            );
            dragSurface.addEventListener(
                DragSurfaceEvent.VERTICAL_INDEX_CHANGED,
                dragSurface_verticalIndexChangedHandler
            );
        }
        else if (instance == debugLabel)
        {
            debugLabel.mouseChildren = false;
            debugLabel.mouseEnabled = false;
        }
    }

    /**
     *  @private
     */
    override protected function partRemoved(partName:String,
                                            instance:Object):void
    {
        super.partRemoved(partName, instance);
        if (instance == dragSurface)
        {
            dragSurface.removeEventListener(
                DragSurfaceEvent.HORIZONTAL_INDEX_CHANGED,
                dragSurface_horizontalIndexChangedHandler
            );
            dragSurface.removeEventListener(
                DragSurfaceEvent.VERTICAL_INDEX_CHANGED,
                dragSurface_verticalIndexChangedHandler
            );
        }
    }

    //--------------------------------------------------------------------------
    //
    //  Overriden methods: UIComponent
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    override protected function commitProperties():void
    {
        super.commitProperties();

        if (minimumChanged || maximumChanged)
        {
            minimumChanged = false;
            maximumChanged = false;
            updateDragSurfaceConstraints(
                getExplicitOrMeasuredWidth(),
                getExplicitOrMeasuredHeight()
            );
            dragValueDirty = true;
        }

        if (dragValueDirty || directionChanged)
        {
            dragValueDirty = false;
            directionChanged = false;
            setValue(nearestValidValue(valueFromDragIndices(dragSurface.horizontalIndex,
                dragSurface.verticalIndex), snapInterval));

            if (userInteraction)
                dispatchEvent(new Event(Event.CHANGE));

            userInteraction = false;
        }

        if (debugLabel)
            debugLabel.text = [
                "horizontalIndex",
                dragSurface.horizontalIndex,
                "verticalIndex",
                dragSurface.verticalIndex,
                "value",
                value
            ].join("\n");
    }

    /**
     *  @private
     */
    override protected function updateDisplayList(unscaledWidth:Number,
                                                  unscaledHeight:Number):void
    {
        super.updateDisplayList(unscaledWidth, unscaledHeight);
        updateDragSurfaceConstraints(unscaledWidth, unscaledHeight);
    }

    //--------------------------------------------------------------------------
    //
    //  Methods
    //
    //--------------------------------------------------------------------------

    /**
     *  Produces a Range value from the horizontal and vertical indices of the
     *  range component.
     */
    protected function valueFromDragIndices(horizontalIndex:Number,
                                            verticalIndex:Number):Number
    {
        var value:Number;
        switch (direction)
        {
            case DragSurfaceRangeDirection.HORIZONTAL:
            {
                value = horizontalIndex + minimum;
                break;
            }
            case DragSurfaceRangeDirection.VERTICAL:
            {
                value = verticalIndex + minimum;
                break;
            }
            default:
            {
                throw new IllegalOperationError(
                    StringUtil.substitute(
                        "Illegal direction value ({0}) for DragSurfaceRange.",
                        direction
                    )
                );
                break;
            }
        }
        return value;
    }

    /**
     *  Makes sure constraints are in sync with the latest component and
     *  dragSurface dimensions.  Called in the context of
     *  <code>updateDisplayList</code>
     */
    protected function updateDragSurfaceConstraints(unscaledWidth:Number,
                                                    unscaledHeight:Number):void
    {
        dragSurface.horizontalIndexFactor =
            (maximum - minimum) / dragSurface.getExplicitOrMeasuredWidth();

        dragSurface.verticalIndexFactor =
            (maximum - minimum) / dragSurface.getExplicitOrMeasuredHeight();
    }

    //--------------------------------------------------------------------------
    //
    //  Event handlers
    //
    //--------------------------------------------------------------------------

    /**
     *  @private
     */
    private function dragSurface_horizontalIndexChangedHandler(
                                            event:DragSurfaceEvent):void
    {
        userInteraction = true;
        dragValueDirty = true;
        invalidateProperties();
    }

    /**
     *  @private
     */
    private function dragSurface_verticalIndexChangedHandler(
                                            event:DragSurfaceEvent):void
    {
        userInteraction = true;
        dragValueDirty = true;
        invalidateProperties();
    }
}
}
